package org.jflame.commons.util;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import org.apache.commons.lang3.exception.ExceptionUtils;

/**
 * 一个简易的重试工具类,
 * 
 * @author charles.zhang
 */
public abstract class SimpleRetry {

    /**
     * 执行可重试的操作,所有异常都会重试
     * 
     * @param <T>
     * @param callable 实际执行操作
     * @param maxAttempts 最大重试次数
     * @param delayInMillis 重试延迟间隔,单位毫秒,可以为0
     * @return
     */
    public static <T> T exec(Callable<T> callable, int maxAttempts, int delayInMillis) {
        return exec(callable, maxAttempts, delayInMillis, null, ex -> true);
    }

    /**
     * 执行可重试的操作
     * 
     * @param <T> 返回结果类型
     * @param callable 实际执行操作
     * @param maxAttempts 最大重试次数
     * @param delayInMillis 重试延迟间隔,单位毫秒,可以为0
     * @param retryPredicate 根据执行结果判断是否需要重试的条件,可以为null
     * @param retryExceptionPredicate 发生异常时继续重试的条件,传null时有任何异常会停止重试并抛出
     * @return
     */
    public static <T> T exec(Callable<T> callable, int maxAttempts, int delayInMillis, Predicate<T> retryPredicate,
            Predicate<Throwable> retryExceptionPredicate) {

        T result = null;
        for (int attemptNumber = maxAttempts; attemptNumber >= 1; attemptNumber--) {
            try {
                result = callable.call();
                if (retryPredicate == null || !retryPredicate.test(result)) {
                    break;
                }
            } catch (Throwable e) {
                e.printStackTrace();
                if (retryExceptionPredicate == null || !retryExceptionPredicate.test(e)) {
                    ExceptionUtils.rethrow(e);
                }
            }
            if (delayInMillis > 0 && attemptNumber > 1) {
                try {
                    TimeUnit.MILLISECONDS.sleep(delayInMillis);
                } catch (InterruptedException e) {
                    Thread.currentThread()
                            .interrupt();
                }
            }
        }
        return result;
    }

    /*public static void main(String[] args) {
        List<Integer> lst = new ArrayList<>();
        Integer r = SimpleRetry.exec(() -> {
            lst.add(4);
            if (lst.size() % 2 == 1) {
                throw new RuntimeException("");
            }
            return lst.size();
        }, 3, 1000);
        System.out.println(r);
    }*/
}
